//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using System.Timers;
    using System.Collections.ObjectModel;
    using System.Windows.Threading;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using System.Windows.Media;
    using System.Windows.Input;
    using System.Globalization;
    using System.Windows.Data;

    /// <summary>
    /// Interaction logic for SubSectionCtrl.xaml
    /// </summary>
    public partial class SubsectionCollapsedCtrl : ITridentUIElement, ISubsectionControl, IDisposable
    {
        /// <summary>
        /// 3 seconds wait time.
        /// </summary>
        private static int DropWaitTime = 500;

        /// <summary>
        /// The subsection model represented by this subsection control.
        /// </summary>
        private TridentSubsectionModel subsectionModel;

        /// <summary>
        /// The composer control containing this control.
        /// </summary>
        private ComposerControl composerControl;

        /// <summary>
        /// Counts the drop wait time.
        /// </summary>
        private Timer dropTimer;

        /// <summary>
        /// The children of this composite element.
        /// </summary>
        private ObservableCollection<ITridentUIElement> childElements = new ObservableCollection<ITridentUIElement>();

        /// <summary>
        /// An empty container holder.
        /// </summary>
        private static Grid EmptyGrid = new Grid();

        /// <summary>
        /// Flag indicating if the expanded ctrl load event has been registered or not.
        /// </summary>
        private bool expanderLoadInitialized;

        /// <summary>
        /// Context menu items which are to be shown only for subsections.
        /// </summary>
        private Collection<MenuItem> subsectionSpecificItems = new Collection<MenuItem>();

        /// <summary>
        /// Creates the collapsed subsection view.
        /// </summary>
        /// <param name="subsectionModel">
        /// The subsection model.
        /// </param>
        public SubsectionCollapsedCtrl(TridentSubsectionModel subsectionModel)
            : this()
        {
            this.AllowDrop = true;
            this.subsectionModel = subsectionModel;
            this.txtBlkHeader.Text = subsectionModel.Label;
            this.txtBlkHeader.ToolTip = SubsectionCollapsedCtrl.GenerateToolTip(subsectionModel.Label, subsectionModel.Description);
            this.Loaded += new RoutedEventHandler(this.OnLoaded);
            this.DragEnter += new DragEventHandler(this.OnDragEnter);
            this.DragLeave += new DragEventHandler(this.OnDragLeave);
            this.Drop += new DragEventHandler(this.OnDrop);
            this.Unloaded += new RoutedEventHandler(this.OnUnload);
            this.MouseDown += new System.Windows.Input.MouseButtonEventHandler(this.OnMouseDown);
            this.btnExpand.Click += new RoutedEventHandler(this.OnSubsectionExpandClick);
            this.Name = "subsection_" + Math.Abs(this.subsectionModel.Label.GetHashCode()).ToString(CultureInfo.InvariantCulture);
            this.dropTimer = new Timer(SubsectionCollapsedCtrl.DropWaitTime);
            this.dropTimer.AutoReset = true;
            this.dropTimer.Elapsed += new ElapsedEventHandler(this.OnWaitTimeElapsed);

            this.ProvenanceMenuClick += new EventHandler<ProvenanceModeEventArgs>(SubsectionCollapsedCtrl_ProvenanceMenuClick);
        }

        void SubsectionCollapsedCtrl_ProvenanceMenuClick(object sender, ProvenanceModeEventArgs e)
        {
            // Note: dummy implementation to avoid warning
        } 

        /// <summary>
        /// Constructor, only initializes.
        /// </summary>
        private SubsectionCollapsedCtrl()
        {
            InitializeComponent();
            this.UpdateClick += delegate(object sender, ModelEventArgs args) { };
            this.Focusable = true;
        } 

        #region Public Properties

        /// <summary>
        /// Gets the subsection model.
        /// </summary>
        public TridentSubsectionModel ContainedSubsectionModel
        {
            get
            {
                return this.subsectionModel;
            }
        }

        /// <summary>
        /// Gets the left flow connection element.
        /// </summary>
        public FrameworkElement LeftControlFlowConnectionElement
        {
            get { return this.rectangleLeft; }
        }

        /// <summary>
        /// Gets the right flow connection element.
        /// </summary>
        public FrameworkElement RightControlFlowConnectionElement
        {
            get { return this.rectangleRight; }
        }

        /// <summary>
        /// The expanded representation for the same model.
        /// </summary>
        public SubsectionExpandedCtrl ExpandedCtrl
        {
            get;
            set;
        }

        #endregion Public Properties

        #region Events

        /// <summary>
        /// Fired when the user wants to remove the subsection only, keeping it's contents intact.
        /// </summary>
        public event EventHandler<RemoveSubsectionEventArgs> RemoveSubsection;

        public event EventHandler<ModelEventArgs> UpdateClick;

        #endregion Events

        #region Delegates

        /// <summary>
        /// Delegate handling expansion from thread.
        /// </summary>
        private delegate void ExpandSubsectionDelegate();

        #endregion Delegates

        /// <summary>
        /// Select the element.
        /// </summary>
        public void OnSelectElement()
        {
            this.OnMouseDown(this, null);
        }

        /// <summary>
        /// Attach event handlers according to the mode.
        /// </summary>
        private void AddInteractionHandlers()
        {
            if (this.composerControl != null && this.composerControl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                this.CreateContextMenu();
            }
            else if (this.composerControl != null && this.composerControl.CurrentRenderMode == RenderMode.ProvenanceMode)
            {
                this.ContextMenu = new ContextMenu();
                this.ContextMenu.Visibility = Visibility.Hidden;
                this.ContextMenu.IsEnabled = false;
            }
        }

        /// <summary>
        /// Remove event handlers according to the mode.
        /// </summary>
        private void RemoveInteractionHandlers()
        {
            if (this.composerControl != null && this.composerControl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                this.ContextMenu = null;
            }
        }

        /// <summary>
        /// Fired on click of ungroup of a subsection.
        /// </summary>
        /// <param name="sender">
        /// ContextMenu.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnSubsectionUngroup(object sender, RoutedEventArgs e)
        {
            if (this.RemoveSubsection != null)
            {
                this.RemoveSubsection.Invoke(sender, new RemoveSubsectionEventArgs(this.subsectionModel));
            }
        }

        /// <summary>
        /// Fired on load of the control.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            this.GetComposerControl();
            this.AddInteractionHandlers();
            this.composerControl.RefreshUI();
        }

        /// <summary>
        /// Fired on load of the control.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnUnload(object sender, RoutedEventArgs e)
        {
            this.RemoveInteractionHandlers();
        }

        /// <summary>
        /// Fired on expand click.
        /// </summary>
        /// <param name="sender">
        /// Expand button.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnSubsectionExpandClick(object sender, RoutedEventArgs e)
        {
            this.ExpandSubsection();
            if (!this.expanderLoadInitialized)
            {
                this.ExpandedCtrl.Loaded += new RoutedEventHandler(this.OnExpandedCtrlLoaded);
                this.expanderLoadInitialized = true;
            }
        }

        private void OnExpandedCtrlLoaded(object sender, RoutedEventArgs e)
        {
            this.ExpandedCtrl.OnSelectElement();            
        }

        /// <summary>
        /// Generates the Stack Panel with name and Description
        /// </summary>
        /// <param name="name">string</param>
        /// <param name="description">string</param>
        /// <returns></returns>
        private static TextBlock GenerateToolTip(string paramName, string paramDescription)
        {
            TextBlock toolTipTxtBlk = new TextBlock();
            toolTipTxtBlk.TextWrapping = TextWrapping.Wrap;
            toolTipTxtBlk.MaxWidth = 500;
            Run nameLabelBlockRun = new Run(UIDesignerCodeConstants.ACTIVITYTOOLTIPNAME);
            nameLabelBlockRun.FontWeight = FontWeights.Bold;
            Run nameTextBlockRun = new Run(paramName);
            Run newLineRun = new Run(Environment.NewLine);
            Run descriptionLabelBlockRun = new Run(UIDesignerCodeConstants.ACTIVITYTOOLTIPDESCRIPTION);
            descriptionLabelBlockRun.FontWeight = FontWeights.Bold;
            Run descriptionTextBlockRun = new Run(paramDescription);

            toolTipTxtBlk.Inlines.Add(nameLabelBlockRun);
            toolTipTxtBlk.Inlines.Add(nameTextBlockRun);
            toolTipTxtBlk.Inlines.Add(newLineRun);
            toolTipTxtBlk.Inlines.Add(descriptionLabelBlockRun);
            toolTipTxtBlk.Inlines.Add(descriptionTextBlockRun);

            return toolTipTxtBlk;
        }

        /// <summary>
        /// Fired on mouse down of the subsection control.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// MouseButtonEventArgs.
        /// </param>
        private void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (this.composerControl != null)
            {
                ITridentUIElement selectedElement = sender as ITridentUIElement;
                if (null == e || e.ChangedButton == MouseButton.Left)
                {
                    this.OnSelectElement(selectedElement);
                }
                else if (e.ChangedButton == MouseButton.Right && !this.composerControl.CurrentSelectedElements.Contains(selectedElement))
                {
                    this.composerControl.CurrentSelectedElements.RemoveAllItems();
                    this.OnSelectElement(selectedElement);
                }

                if (e != null)
                {
                    // Handles the logic of double click.
                    if (2 == e.ClickCount && this.composerControl.CurrentRenderMode == RenderMode.ComposerMode)
                    {
                        this.ShowActivityProperties();
                    }

                    e.Handled = true;
                }
            }
        }

        /// <summary>
        /// Show the property pane.
        /// </summary>
        protected void ShowActivityProperties()
        {
            if (this.ShowProperties != null)
            {
                this.ShowProperties.Invoke(this, new EventArgs());
            }
        }

        private void OnSelectElement(ITridentUIElement selectedElement)
        {
            if (!(Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)))
            {
                this.composerControl.CurrentSelectedElements.RemoveAllItems();
            }

            if (null != selectedElement)
            {
                this.composerControl.CurrentSelectedElements.SelectChildrenUpTo(selectedElement);
                Keyboard.Focus(selectedElement as UIElement);
                this.Focus();
            }
        }

        /// <summary>
        /// Get the composer control instance.
        /// </summary>
        private void GetComposerControl()
        {
            string composerCtrlName = (string)this.GetValue(ComposerControl.ComposerControlNameProperty);
            this.composerControl = Application.Current.MainWindow.FindName(composerCtrlName) as ComposerControl;
        }

        /// <summary>
        /// Expand the subsection.
        /// </summary>
        private void ExpandSubsection()
        {
            if (this.subsectionModel != null)
            {
                this.subsectionModel.IsCollapsed = false;
                
            }
        }

        /// <summary>
        /// Fired on drag enter onto the collapsed subsection.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// DragEventArgs.
        /// </param>
        private void OnDragEnter(object sender, DragEventArgs e)
        {
            this.dropTimer.Start();
        }

        /// <summary>
        /// Fired on drag leave of the collapsed subsection.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// DragEventArgs.
        /// </param>
        private void OnDragLeave(object sender, DragEventArgs e)
        {
            this.dropTimer.Stop();
        }

        /// <summary>
        /// Fired on elapse of the wait time.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// ElapsedEventArgs.
        /// </param>
        private void OnWaitTimeElapsed(object sender, ElapsedEventArgs e)
        {
            this.Dispatcher.Invoke(DispatcherPriority.Normal, new ExpandSubsectionDelegate(this.ExpandSubsection));
            this.dropTimer.Stop();
        }

        /// <summary>
        /// Fired on drop.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// DragEventArgs.
        /// </param>
        private void OnDrop(object sender, DragEventArgs e)
        {
            this.dropTimer.Stop();
        }

        /// <summary>
        /// The position where the binding connection has to start at.
        /// </summary>
        /// <param name="relativeTo">The element relative to which the Position is to be determined.</param>
        /// <returns>The connection point.</returns>
        public Point GetBindingStartPoint(UIElement relativeTo)
        {
            if (relativeTo == null)
            {
                throw new ArgumentNullException("relativeTo");
            }

            return this.TranslatePoint(new Point(this.ActualWidth - 2.0, ((this.ActualHeight / 2.0) + 20)), relativeTo);
        }

        /// <summary>
        /// The position where the binding connection has to end at.
        /// </summary>
        /// <param name="relativeTo">The element relative to which the Position is to be determined.</param>
        /// <returns>The connection point.</returns>
        public Point GetBindingEndpoint(UIElement relativeTo)
        {
            if (relativeTo == null)
            {
                throw new ArgumentNullException("relativeTo");
            }

            return this.TranslatePoint(new Point(2, ((this.ActualHeight / 2.0) + 20)), relativeTo);
        }

        #region ICompositeUiElement Members

        public ObservableCollection<ITridentUIElement> ChildElements
        {
            get
            {
                return this.childElements;
            }
        }

        public void AddChildElement(ITridentUIElement childElement)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            #endregion Parameters check.

            this.childElements.Add(childElement);
        }

        public void InsertChildElement(ITridentUIElement childElement, int indexToInsertAt)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            if (0 > indexToInsertAt)
            {
                throw new ArgumentOutOfRangeException("indexToInsertAt");
            }

            #endregion Parameters check.

            this.childElements.Insert(indexToInsertAt, childElement);
        }

        public bool RemoveChildElement(ITridentUIElement childElement)
        {
            #region Parameters check.

            if (null == childElement)
            {
                throw new ArgumentNullException("childElement");
            }

            #endregion Parameters check.

            bool opStatus = false;
            if (this.childElements.Contains(childElement))
            {
                opStatus = this.childElements.Remove(childElement);
            }

            return opStatus;
        }

        public void RemoveChildElementAt(int indexOfChild)
        {
            #region Parameters check.

            if (0 > indexOfChild || (this.childElements.Count - 1) < indexOfChild)
            {
                throw new ArgumentOutOfRangeException("indexOfChild");
            }

            #endregion Parameters check.

            this.childElements.RemoveAt(indexOfChild);
        }

        public Panel MainContentHolder
        {
            get { return SubsectionCollapsedCtrl.EmptyGrid; }
        }

        #endregion

        public event EventHandler DeleteElement;

        public event EventHandler SelectElement;

        public event EventHandler ShowProperties;

        public event EventHandler InsertIntoSubsection;

        public event EventHandler<ProvenanceModeEventArgs> ProvenanceMenuClick;

        public void ApplySelectedStyle()
        {
            this.brdr1.BorderBrush = Brushes.Orange;
            this.brdr2.BorderBrush = Brushes.Orange;
            this.brdr3.BorderBrush = Brushes.Orange;
            this.brdrMainDisp.BorderBrush = Brushes.Orange;
        }

        public void ApplyUnselectedStyle()
        {
            Color regularColor = (Color)ColorConverter.ConvertFromString("#FFC6D3D8");
            this.brdr1.BorderBrush = new SolidColorBrush(regularColor);
            this.brdr2.BorderBrush = new SolidColorBrush(regularColor);
            this.brdr3.BorderBrush = new SolidColorBrush(regularColor);
            this.brdrMainDisp.BorderBrush = new SolidColorBrush(regularColor);
        }

        public int FetchUIElementPosition()
        {
            int index = -1;
            if (this.Model.ParentModel != null && this.composerControl != null && this.composerControl.UIElements.ContainsKey(this.Model.ParentModel.UniqueId))
            {
                ICompositeUIElement parentElem = this.composerControl.UIElements[this.Model.ParentModel.UniqueId] as ICompositeUIElement;
                if (parentElem != null && parentElem.ChildElements.Contains(this))
                {
                    index = parentElem.ChildElements.IndexOf(this);
                }
            }

            return index;
        }

        public BaseModel Model
        {
            get
            {
                return this.subsectionModel;
            }
        }

        public void CreateContextMenu()
        {
            this.ContextMenu = new ContextMenu();
            MenuItem deleteMenu = new MenuItem();
            deleteMenu.Header = TridentResourceManager.GetString("DeleteActivityLabel");
            deleteMenu.Icon = TridentUIActivityElement.GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ActivitesDelete.png");
            deleteMenu.Name = "deleteSubsection";
            deleteMenu.Click += new RoutedEventHandler(this.OnDelete);
            this.ContextMenu.Items.Add(deleteMenu);

            if (SubsectionMetadata.Data != null)
            {
                MenuItem insertIntoSubsectionMenu = new MenuItem();
                insertIntoSubsectionMenu.Header = TridentResourceManager.GetString("CreateSubsection");
                insertIntoSubsectionMenu.Name = "createSubsection";
                insertIntoSubsectionMenu.Icon = TridentUIActivityElement.GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/EmptySubSection.png");
                insertIntoSubsectionMenu.Click += new RoutedEventHandler(this.OnInsertIntoSubsectionClick);
                this.ContextMenu.Items.Add(insertIntoSubsectionMenu);
            }

            MenuItem propertyPaneMenu = new MenuItem();
            propertyPaneMenu.Header = TridentResourceManager.GetString("PropertiesContextMenuLabel");
            propertyPaneMenu.Name = "propertyPaneMenu";
            propertyPaneMenu.Icon = TridentUIActivityElement.GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ActivitiesProperties.png");
            propertyPaneMenu.Click += new RoutedEventHandler(this.OnPropertiesClicked);
            this.ContextMenu.Items.Add(propertyPaneMenu);

            MenuItem ungroupItem = new MenuItem();
            ungroupItem.Header = "Remove Subsection";
            ungroupItem.Name = "removeSubsection";
            ungroupItem.Icon = TridentUIActivityElement.GetIcon(@"pack://Application:,,,/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/removeSubsection.png");
            ungroupItem.ToolTip = "Removes only the subsection, " + Environment.NewLine + "keeping the contents of the subsection intact.";
            ungroupItem.Click += new RoutedEventHandler(this.OnSubsectionUngroup);
            this.ContextMenu.Items.Add(ungroupItem);
            this.subsectionSpecificItems.Add(ungroupItem);

            this.ContextMenu.Opened += new RoutedEventHandler(this.OnContextMenuOpened);
        }

        private void OnContextMenuOpened(object sender, RoutedEventArgs e)
        {
            if (this.composerControl.CurrentSelectedElements.OnlySubsections)
            {
                foreach (MenuItem item in this.subsectionSpecificItems)
                {
                    item.Visibility = Visibility.Visible;
                }
            }
            else
            {
                foreach (MenuItem item in this.subsectionSpecificItems)
                {
                    item.Visibility = Visibility.Collapsed;
                }
            }
        }

        public void OnDeleteClick()
        {
            if (this.DeleteElement != null)
            {
                this.DeleteElement.Invoke(this, new EventArgs());
            }
        }

        public void OnSelectClick()
        {
            if (this.SelectElement != null)
            {
                this.SelectElement.Invoke(this, new EventArgs());
            }
        }
        
        private void OnDelete(object sender, RoutedEventArgs e)
        {
            this.OnDeleteClick();
        }

        private void OnPropertiesClicked(object sender, RoutedEventArgs e)
        {
            if (this.ShowProperties != null)
            {
                this.ShowProperties.Invoke(this, new EventArgs());
            }
        }

        private void OnInsertIntoSubsectionClick(object sender, RoutedEventArgs e)
        {
            if (this.InsertIntoSubsection != null)
            {
                this.InsertIntoSubsection.Invoke(this, new EventArgs());
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // The bulk of the clean-up code is implemented in Dispose(bool)
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.dropTimer != null)
                {
                    this.dropTimer.Dispose();
                }
            }
        }

        #endregion
    }
}